/********************************************************************
 * Copyright(c) 2023-2023 Grayson Zheng.
 * File: uart.C
 * Description: LCD1602 driver for AT89C52.
 * Version: 1.1
 * Date: 2024-01-04
 * Author: zhengxinyu13@qq.com
 * ---------- Revision History ----------
 * <version>
 * <date>
 * <author>
 * <desc>
 * Modified to be suitable to the new coding rules in all functions.
 ********************************************************************/

#include "LCD1602.h"
#include "delay.h"

/* Check if LCD1602 is in a busy state, and wait if it is. */
void lcd1602_busy_detection(void)
{
	LCD_DATA = 0xFF;

	do {
		RESET_BIT(LCD_EN);
		RESET_BIT(LCD_RS);
		SET_BIT(LCD_RW);
		SET_BIT(LCD_EN);
	} while (LCD_BUSY);

	RESET_BIT(LCD_EN);
}

/* Command Write Timing for LCD1602. */
void lcd1602_write_command(uint_8 command)
{
	lcd1602_busy_detection();

	RESET_BIT(LCD_RS);
	RESET_BIT(LCD_RW);

	LCD_DATA = command;
	SET_BIT(LCD_EN);
	delay_10us();

	RESET_BIT(LCD_EN);
	delay_10us();
}

/* Data Write Timing for LCD1602. */
void lcd1602_write_data(uint_8 dat)
{
	lcd1602_busy_detection();

	SET_BIT(LCD_RS);
	RESET_BIT(LCD_RW);

	LCD_DATA = dat;
	SET_BIT(LCD_EN);
	delay_10us();

	RESET_BIT(LCD_EN);
	delay_10us();
}

void lcd1602_init(void)
{
	lcd1602_write_command(FUNCTION_SET | _8_BIT_INTERFACE_ | _16_BY_2_ | _5_BY_8_DOTS_);
	lcd1602_write_command(DISPLAY_CONTROL | DISPLAY_OFF | CURSOR_OFF | CURSOR_BLINK_OFF);
	lcd1602_write_command(CLEAR_DISPLAY);
	lcd1602_write_command(ENTRY_MODE_SET | INC_CURSOR | NO_DISPLAY_SHIFT);
	lcd1602_write_command(DISPLAY_CONTROL | DISPLAY_ON | CURSOR_OFF | CURSOR_BLINK_OFF);
}

/* Clear the LCD1602 screen. */
void lcd1602_clear_screen(void)
{
	lcd1602_write_command(CLEAR_DISPLAY);
	delay_xms(2);
}

void lcd1602_shift_left(void)
{
	lcd1602_write_command(CURSOR_SHIFT | SHIFT_DISPLAY | SHIFT_LEFT);
	delay_xms(1);
}

void lcd1602_shift_right(void)
{
	lcd1602_write_command(CURSOR_SHIFT | SHIFT_DISPLAY | SHIFT_RIGHT);
	delay_xms(1);
}

/* Set the cursor position.
 * row: row-coordinate, range: 0-1
 * column: column-coordinates, range: 0-15
 */
void lcd1602_set_cursor(LINE row, uint_8 column)
{
	if (ROW_0 == row)
		lcd1602_write_command(DDRAM_ADDR | (column));
	else if (ROW_1 == row)
		lcd1602_write_command(DDRAM_ADDR | (column) + CGRAM_ADDR);
}

/* Display a character at the specified position in LCD1602. */
void lcd1602_show_char(LINE row, uint_8 column, uint_8 character)
{
	lcd1602_set_cursor(row, column);
	lcd1602_write_data(character);
}

/* Display the given string at the specified position in LCD1602. */
void lcd1602_show_string(LINE row, uint_8 column, uint_8 *string)
{
	lcd1602_set_cursor(row, column);
	while (*string)
		lcd1602_write_data(*string++);
}

int_16 lcd1602_power(int_16 x, int_16 y)
{
	int_16 result = 1;

	while (y--)
		result *= x;

	return result;
}

uint_8 lcd1602_count_digits(int_16 number)
{
	int digit_count = 0;

	if (number < 0)
		number = -number;

	 if (0 == number)
		return 1;

	while (number != 0) {
		number /= 10;
		digit_count++;
	}

	return digit_count;
}

/* Display the given number in decimal at the specified position in LCD1602. */
void lcd1602_show_number(LINE row, uint_8 column, int_16 number)
{
	uint_8 length = lcd1602_count_digits(number);

	lcd1602_set_cursor(row, column);

	if (number < 0) {
		number = -number;
		lcd1602_write_data('-');
	}

	while (length--)
		lcd1602_write_data('0' + number / lcd1602_power(10, length) % 10); 
}

/* Display the given number in hexadecimal at the specified position in LCD1602. */
void lcd1602_show_hex(LINE row, uint_8 column, uint_16 number)
{
	int_8 i;
	int_8 hexadecimal;

	lcd1602_set_cursor(row, column);
	for (i = 3; i >= 0; i--) {
		hexadecimal = (number >> (4 * i)) & 0x000F;
		if(hexadecimal < 10)
			lcd1602_write_data('0' + hexadecimal); 
		else
			lcd1602_write_data('A' + hexadecimal - 10);
}

}

/* Display the given number in binary at the specified position in LCD1602. */
void lcd1602_show_bin(LINE row, uint_8 column, uint_16 number)
{
	int_8 i;

	lcd1602_set_cursor(row, column);
	for (i = 15; i >= 0; i--) 
		lcd1602_write_data('0' + (number >> i) % 2);
}
